package xpath

import (
	"bytes"
	"strings"

	"code.google.com/awwwe-browser/data"
)

// an expression is boolean
// ex: (count(p1) = 1 or p2) and count(p3) = 3
type parsableExpression struct {
	expression []rune
	index      int
}

func newParsableExpression(expression []rune) *parsableExpression {
	var v parsableExpression
	v.expression = unwrapRunes(expression)
	v.index = 0
	return &v
}

func (this *parsableExpression) skipWhiteSpace() {
	length := len(this.expression)
	for ; this.index < length; this.index++ {
		if strings.ContainsRune(data.WhiteSpaceString, this.expression[this.index]) == false {
			break
		}
	}
}

// simple exptractions

func (this *parsableExpression) extractString() (string, bool) {
	var quote rune
	this.skipWhiteSpace()
	mark := this.index
	length := len(this.expression)
	if this.index < length {
		if this.expression[this.index] == data.DQuoteRune || this.expression[this.index] == data.SQuoteRune {
			quote = this.expression[this.index]
			// in quote
			for this.index++; this.index < length && this.expression[this.index] != quote; this.index++ {
			}
			if this.expression[this.index] == quote {
				runes := this.expression[mark+1 : this.index]
				this.index++
				return data.RunesToUTF8String(runes), true
			}
		}
	}
	return data.EmptyString, false
}

func (this *parsableExpression) extractRunes(runes []rune) bool {
	this.skipWhiteSpace()
	length := len(runes)
	ok := false
	if this.index+length <= len(this.expression) {
		for i := 0; i < length; i++ {
			ok = this.expression[this.index+i] == runes[i]
			if ok == false {
				break
			}
		}
	}
	if ok {
		this.index += length
	}
	return ok
}

func (this *parsableExpression) extractText(text string) bool {
	runes := bytes.Runes([]byte(text))
	return this.extractRunes(runes)
}

func (this *parsableExpression) extractInt() (int, bool) {
	this.skipWhiteSpace()
	mark := this.index
	length := len(this.expression)
	v := 0
	sign := 0
	for {
		if this.index < length {
			r := this.expression[this.index]
			if sign == 0 {
				if r == '-' {
					sign = -1
					this.index++
					continue
				} else if r == '+' {
					sign = 1
					this.index++
					continue
				}
			}
			if r >= '0' && r <= '9' {
				if sign == 0 {
					sign = 1
				}
				v *= 10
				v += int(r) - '0'
				this.index++
			} else {
				// unwanted rune
				break
			}
		} else {
			break
		}
	}
	return v * sign, this.index != mark
}

func (this *parsableExpression) extractIndex() (int, bool) {
	returnInt := 0
	ok := false
	this.skipWhiteSpace()
	mark := this.index
	if this.expression[this.index] == bracketStartRune {
		this.index++
		returnInt, ok = this.extractInt()
		if ok {
			this.skipWhiteSpace()
			ok = this.expression[this.index] == bracketEndRune
		}
	}
	if ok {
		this.index++
	} else {
		this.index = mark
	}
	return returnInt, ok
}

func (this *parsableExpression) extractComparitor() (comparisonType, bool) {
	this.skipWhiteSpace()
	ctype := comparisonTypeNil
	if this.index < len(this.expression) {
		if this.expression[this.index] == data.EqualsRune {
			ctype = comparisonTypeEqual
			this.index++
		} else if this.extractRunes(notEqualRunes) {
			ctype = comparisonTypeNotEqual
		} else if this.extractRunes(lessThanRunes) {
			ctype = comparisonTypeLessThan
		} else if this.extractRunes(greaterThanRunes) {
			ctype = comparisonTypeGreaterThan
		} else if this.extractRunes(lessThanEqualRunes) {
			ctype = comparisonTypeLessThanEqual
		} else if this.extractRunes(greaterThanEqualRunes) {
			ctype = comparisonTypeGreaterThanEqual
		}
	}
	return ctype, ctype != comparisonTypeNil
}

// extract tests

// extract "count(path) = 1" from this.expression
// advance this.index
// returns the path, the comparison type, the value and ok (ok = if path and value were extracted)
func (this *parsableExpression) extractCountTest() ([]rune, comparisonType, int, bool) {
	returnPath := emptyRunes
	returnCtype := comparisonTypeNil
	returnCount := 0
	ok := false
	this.skipWhiteSpace()
	mark := this.index
	length := len(this.expression)
	if this.extractRunes(countStartRunes) {
		start := this.index
		count := 0
		this.skipWhiteSpace()
		for {
			if this.index == length {
				break
			}
			if this.expression[this.index] == parenStartRune {
				if count == 0 {
					if this.index < length-1 {
						start = this.index + 1
					}
				}
				count++
			} else if this.expression[this.index] == parenEndRune {
				count--
			}
			if count == 0 {
				returnPath = this.expression[start:this.index]
				this.index++
				break
			}
			this.index++
		}
		if this.index < length {
			this.skipWhiteSpace()
			returnCtype, ok = this.extractComparitor()
			if ok {
				returnCount, ok = this.extractInt()
			}
		}
	}
	if ok == false {
		this.index = mark
	}
	return returnPath, returnCtype, returnCount, ok
}

// extract "exists(path)" from this.expression
// advance this.index
// returns the path and ok (ok = if path was extracted)
func (this *parsableExpression) extractExistsTest() ([]rune, bool) {
	returnPath := emptyRunes
	ok := false
	this.skipWhiteSpace()
	mark := this.index
	length := len(this.expression)
	if this.extractRunes(existsStartRunes) {
		start := this.index
		count := 0
		this.skipWhiteSpace()
		for {
			if this.index == length {
				break
			}
			if this.expression[this.index] == parenStartRune {
				if count == 0 {
					if this.index < length-1 {
						start = this.index + 1
					}
				}
				count++
			} else if this.expression[this.index] == parenEndRune {
				count--
			}
			if count == 0 {
				returnPath = this.expression[start:this.index]
				this.index++
				ok = true
				break
			}
			this.index++
		}
	}
	if ok == false {
		this.index = mark
	}
	return returnPath, ok
}

// parenthesized extractions

func (this *parsableExpression) extractParenWrapped() ([]rune, bool) {
	length := len(this.expression)
	returnPath := emptyRunes
	ok := false
	if this.index < length {
		this.skipWhiteSpace()
		mark := this.index
		count := 0
		if this.expression[this.index] == parenStartRune {
			count++
			this.index++
			for ; this.index < length; this.index++ {
				if this.expression[this.index] == parenStartRune {
					count++
				} else if this.expression[this.index] == parenEndRune {
					count--
				}
				if count == 0 {
					returnPath = this.expression[mark+1 : this.index]
					ok = true
					this.index++
					break
				}
			}
		}
		if ok == false {
			this.index = mark
		}
	}
	return returnPath, ok
}

func (this *parsableExpression) extractParenIndexed() ([]rune, int, bool) {
	returnPath := emptyRunes
	returnIndex := 0
	var ok bool
	this.skipWhiteSpace()
	mark := this.index
	returnPath, ok = this.extractParenWrapped()
	if ok {
		returnIndex, ok = this.extractIndex()
	}
	if !ok {
		this.index = mark
	}
	return returnPath, returnIndex, ok
}
